//== SAGITTARIUS ============================================================================

//== BEGINNING OF CODE ===============================================================

#include "sagittarius/core/SagittariusCore.h"
#include "sagittarius/symtab/symtab.h"
#include "sagittarius/hash/hash.h"
#include "sagittarius/io/io.h"
#include <malloc.h>
#include <string.h>

#if SAGITTARIUS_COMPILER == SAGITTARIUS_COMPILER_GNUC || SAGITTARIUS_COMPILER == SAGITTARIUS_COMPILER_MINGW
inline
#endif
symtab_kvpair make_symtab_kvpair(symtab* st, constLexBuf name, size_t l) {
    symtab_kvpair p;
    p.id.k = sg_lex_hashbufl(name, l);
    p.name = sg_lex_copybufl(name, l);
    p.id.u = symtab_count_k(st, p.id, name);
    //WLSEMA(st->sema); //no modifications to the hash table internals while this is running
    
    //WUSEMA(st->sema);
    return p;
}

#if SAGITTARIUS_COMPILER == SAGITTARIUS_COMPILER_GNUC || SAGITTARIUS_COMPILER == SAGITTARIUS_COMPILER_MINGW
inline
#endif
void delete_symtab_kvpair(symtab_kvpair p) {
    LX_FREE_BUFFER(p.name);
}

uint8 symtab_count_k(symtab* st, symid id, constLexBuf name) {
    symtab_bucket* b;
    uint32 i;
    uint64 c=0;
    symid mask;
    //the order is very important here
    mask.k = -1;
    mask.u = 0;
    //masks out symid.u
    
    RLSEMA(st->sema);
    
    b=&st->b[id.k%st->nb];
    for(i=0; i<b->n; ++i) {
        if((b->e[i].id.k&mask.k) == (id.k&mask.k) && !sg_lex_bufeq(name, b->e[i].name))
            ++c;
    }
    
    RUSEMA(st->sema);
    
    return c;
}

symcheck symtab_check(symtab* st, symid e) {
    symtab_bucket* b;
    uint32 i;
    symcheck c;
    c.b = 0;
    
    RLSEMA(st->sema);
    
    b=&st->b[e.k%st->nb];
    for(i=0; i<b->n; ++i) {
        if(b->e[i].id.k == e.k) {
            c.b = 1;
            c.id = b->e[i].id;
            break; //can return early
        }
    }
    
    RUSEMA(st->sema);
    
    return c;
}

LexBuf symtab_name(symtab* st, symid e) {
    symtab_bucket* b;
    uint32 i;
    
    RLSEMA(st->sema);
    
    b=&st->b[e.k%st->nb];
    for(i=0; i<b->n; ++i) {
        if(b->e[i].id.k == e.k) {
            RUSEMA(st->sema);
            return b->e[i].name;
        }
    }
    
    RUSEMA(st->sema);
    
    return NULL;
}

LexBuf symtab_namek(symtab* st, symcode k) {
    symid e;
    e.k = k;
    return symtab_name(st, e);
}

int _symtab_insert(symtab* st, symtab_kvpair e) {
    symtab_bucket* b = NULL;
    size_t bn;
    symtab_kvpair* tmp;
    uint32 i;
    uint32 depth;
    
    /*SG_WRITE(2, "pre-lock\n");
    sg_writeUInt(2, st->sema);
    SG_WRITE(2, "\n");*/
    WLSEMA(st->sema); //no modifications to the hash table internals while this is running
    //we can do reader lock on table & writer lock on bucket or just writer lock on table
    //SG_WRITE(2, "post-lock\n");
    
     //no modifications to the hash table internals while this is running
    
    bn = e.id.k%st->nb; //NOTE: not safe to do this before st sema is locked
    b = &st->b[bn]; //NOTE: not safe to do this before st sema is locked
    
    for(i=0; i<b->n; ++i) {
        if(b->e[i].id.k == e.id.k) {
            WUSEMA(st->sema);
            return HASH_COLLISION;
        }
    }
    if(b->n >= b->depth) {
        //need to reallocate
        depth = b->depth<<1;
        tmp = malloc(depth*sizeof(symtab_kvpair));
        memcpy(tmp, b->e, b->depth*sizeof(symtab_kvpair));
        b->depth = depth;
        free(b->e);
        b->e = tmp;
    }
    b->e[b->n] = e;
    ++b->n;
    
    WUSEMA(st->sema);
    
    
    return HASH_SUCCESS;
}

symid symtab_get_or_insertl(symtab* st, constLexBuf name, size_t l) {
    symcheck c;
    symtab_kvpair e=make_symtab_kvpair(st, name, l);
    c = symtab_check(st, e.id);
    if(c.b) {
        delete_symtab_kvpair(e);
        return c.id;
    } else {
        _symtab_insert(st, e);
    }
    return e.id;
}

symcode symtab_get_or_insert_cstr(symtab* st, char* buf) {
    symcode s;
    LexBuf w;
    
    w = sg_lex_str2buf(buf);
    s = symtab_get_or_insertl(st, w, LX_BUFFER_LEN(w)-1).k;
    LX_FREE_BUFFER(w);
    
    return s;
}

symtab* symtab_rehash(symtab* st, uint32 nbuckets, uint32 depth) {
    symtab* h;
    symtab_bucket* b;
    uint32 i, j;
    
    if(st) {
        RLSEMA(st->sema);
    }
    
    h = malloc(sizeof(symtab));
    AN(h);
    
    INITSEMA(h->sema);
    
    h->nb = nbuckets;
    h->b = malloc(nbuckets*sizeof(symtab_bucket));
    for(i=0; i<h->nb; ++i) {
        h->b[i].n = 0;
        h->b[i].depth = depth;
        h->b[i].e = malloc(depth*sizeof(symtab_kvpair));
        
    }
    
    //insert old values
    if(st != NULL) {
        for(i=0; i<st->nb; ++i) {
            b = &st->b[i];
            for(j=0; j<b->n; ++j) {
                _symtab_insert(h, b->e[j]);
            }
        }
    }
    
    if(st) {
        RUSEMA(st->sema);
    }
    
    return h;
}

void finalize_symtab_bucket(symtab_bucket* b) {
    uint32 i;
    for(i=0; i<b->n; ++i) {
        LX_FREE_BUFFER(b->e[i].name);
    }
    free(b->e);
}

void finalize_symtab(symtab* h) {
    uint32 i;
    for(i=0; i<h->nb; ++i) {
        finalize_symtab_bucket(&h->b[i]);
    }
    free(h->b);
    free(h);
}

void symtabPrintSym(int fd, symtab* st, symid e) {
    /*sg_writeUInt64(fd, e.k);
    SG_WRITE(fd, "(");*/
    LX_WRITE_BUF(fd, symtab_name(st, e));
    //SG_WRITE(fd, ")\n");
}

void symtabPrintSymK(int fd, symtab* st, symcode k) {
    /*sg_writeUInt64(fd, e.k);
    SG_WRITE(fd, "(");*/
    symid e;
    e.k = k;
    LX_WRITE_BUF(fd, symtab_name(st, e));
    //SG_WRITE(fd, ")\n");
}

void symtab_dumpstate(int fd, symtab* h) {
    uint32 i, j;
    SG_WRITE(fd, "Hash Table64");
    SG_WRITE(fd, "\n  No buckets: ");
    sg_writeInt(fd, (int)h->nb);
    SG_WRITE(fd, "\n");
    for(i=0; i<h->nb; ++i) {
        SG_WRITE(fd, "    Bucket ");
        sg_writeUInt(fd, i);
        SG_WRITE(fd, "\n      Number ");
        sg_writeUInt(fd, h->b[i].n);
        SG_WRITE(fd, "\n      Depth ");
        sg_writeUInt(fd, h->b[i].depth);
        SG_WRITE(fd, "\n");
        
        for(j=0; j<h->b[i].n; ++j) {
            SG_WRITE(fd, "        Key 0x");
            //sg_writeMemSegHex(fd, &h->b[i].e[j].id.k, 8);
            sg_writeUInt64Hex(fd, h->b[i].e[j].id.k);
            SG_WRITE(fd, "\n        Name ");
            LX_WRITE_BUF(fd, h->b[i].e[j].name);
            SG_WRITE(fd, "\n");
        }
    }
}